#include <Arduino.h>
#include <WiFi.h>
#include <Ticker.h>
#include <EEPROM.h>
#include "SPIFFS.h"
#include <httpUpdate.h>
#include <BLEDevice.h>
int R1 = 200000; // 360000欧姆
int R2 = 10000;  // 5000欧姆
int R3 = 100000; // 180000欧姆
int R4 = 10000;  // 10000欧姆

float Ux = 14200;      // 定义蓄电池变量电压
float Uxm = 14200;     // 设置蓄电池满电电压
float Ixm = 10000;     // 设置蓄电池满电电流
float Uxq = 1000;      // 设置蓄电池启动电压
float Uxf = 13800;     // 设置蓄电池浮充电压
float Uxc = 13000;     // 设置蓄电池解除浮充电压
float Utq_min = 8000;  // 初始化太阳能关闭电压
float Utq_max = 14000; // 初始化太阳能启动电压
float Utg_max = 0;     // 初始化太阳能追踪最大电压
float Utg_min = 0;     // 初始化太阳能追踪最小电压
float Utg = 0;         // 初始化太阳能追踪电压

float It = 30000; // 设置充电电流
float T = 50;     // 设置温度保护

int t = 100;    // 延迟时间/ms
int s = 100;    // 扰动弧度单位/mv
int Duty = 100; // 定义 PWM 占空比的初始值

const int freq = 20000;                        // 设置PWM的频率
const int mos_pwm = 0;                         // 设置PWM的通道
const int fan_pwm = 1;                         // 设置PWM的通道
const int resolution = 10;                     // 设置PWM 占空比的分辨率
const int maxpwmvalue = (1 << resolution) - 1; // 占空比的最大值

const int PWM_PIN = 48; // PWM 信号输出引脚0-18
const int SD_PIN = 45;  // PWM 信号使能引脚0-18
const int tyn_PIN = 2;  // 太阳能使能引脚
const int fan_PIN = 1;  // 蜂鸣器引脚
const int spk_PIN = 46; // 蜂鸣器引脚

float ble_U, ble_U1, ble_U2, ble_U3, ble_U4, ble_U1234, ble_I, ble_T1, ble_T2, ble_IN, ble_OUT, ble_dl; // 读取的电池组参数
float tyn_U, tyn_I, tyn_P, xdc_U, xdc_I, Q, kWh, MOS_T, zuoTiankWh;                                     // 读取的太阳能参数

boolean ble = true;     // 蓝牙使能
boolean in = false;     // 输出使能
boolean out = true;     // 充电开关
boolean mppt = true;    // 智能开关
boolean cdzt = false;   // 蓄电池充电状态浮充
boolean update = false; //

const char *ntpServer = "pool.ntp.org";
const long gmtOffset_sec = 8 * 3600;
const int daylightOffset_sec = 0;
/**
 * 结构体，用于存储配网信息
 */
struct config_type
{
  uint8_t mppt;
  uint8_t reboot;
  int data;
  int Uxm; // 设置蓄电池满电电压
  int Uxf; // 设置蓄电池浮充电压
  int Ixm; // 设置蓄电池满电电流
  int It;  // 设置充电电流
  int T;   // 设置温度保护
};
config_type config;
uint8_t *p = (uint8_t *)(&config);
Ticker timerTick; // 创建 Ticker 对象
tm timeinfo;
String rebootReason = ""; // 重启原因
hw_timer_t *tim = NULL;   // 声明看门狗指针变量

void loadConfig(); // 读取储存数据
void saveConfig(); // 保存数据
void yourFunction();
void doHttpStick(String msg);
void operateOnDataInFile(const char *filename, String targetChar, String action, float &newData);
bool processFile(String targetValue, char parameterData[]);

#include <esp32_Bfy.h>
#include <esp32_Adc.h>
#include <esp32_Mppt.h>
#include <esp32_Udp.h>
#include <esp32_ble.h>
#include <esp32_1602.h>

void delayRestart(float t)
{
  Ticker delayTimer;
  delayTimer.attach(t, []()
                    { ESP.restart(); });
}

/**
 * 恢复出厂设置，清除存储的wifi信息
 */
void restoreFactory()
{
  Serial.println("Restore Factory....... ");
  config.mppt = 0xAA;
  config.Uxm = 14500; // 设置蓄电池满电电压
  config.Uxf = 13800; // 设置蓄电池浮充电压
  config.Ixm = 10000; // 设置蓄电池满电电流
  config.It = 30000;  // 设置充电电流
  config.T = 50;      // 设置温度保护
  config.reboot = 0;
  saveConfig();
  delayRestart(1);
  while (1)
  {
    delay(100);
  }
}
/**
 * 存储配网信息
 */
void saveConfig()
{
  EEPROM.begin(256);
  for (int i = 0; i < sizeof(config); i++)
  {
    EEPROM.write(i, *(p + i));
  }
  EEPROM.commit();
}

/**
 * 加载存储的信息，并检查是否进行了反复5次重启恢复出厂信息
 */
void loadConfig()
{
  uint8_t mac[6];
  WiFi.macAddress(mac);
  EEPROM.begin(256);
  for (int i = 0; i < sizeof(config); i++)
  {
    *(p + i) = EEPROM.read(i);
  }

  config.reboot = config.reboot + 1;
  saveConfig();

  Serial.printf("config.reboot:%d\n", config.reboot);
  delay(1000);

  if (config.reboot >= 10 || config.mppt != 0xAA)
  {
    restoreFactory();
  }

  config.reboot = 0;
  saveConfig();

  if (!SPIFFS.begin()) // 启动闪存文件系统
  {
    Serial.println("begin");
    SPIFFS.format();
  }
  else // 读取SPIFFS分区
  {
    Serial.println("SPIFFS OK!");
    // 检查文件是否存在
    if (!SPIFFS.exists("/data.txt") && !SPIFFS.exists("/charts.txt"))
    {
      Serial.println("新建文件");
      File zws1 = SPIFFS.open("/data.txt", "w");
      File zws2 = SPIFFS.open("/charts.txt", "w");
    }
    // uint32_t totalBytes = SPIFFS.totalBytes();
    // uint32_t usedBytes = SPIFFS.usedBytes();
    // Serial.print("总空间: ");
    // Serial.print(totalBytes);
    // Serial.println(" 字节");
    // Serial.print("已使用空间: ");
    // Serial.print(usedBytes);
    // Serial.println(" 字节");
    // Serial.print("剩余空间: ");
    // Serial.print(totalBytes - usedBytes);
    // Serial.println(" 字节");
    File zws1 = SPIFFS.open("/data.txt", "r");
    // Serial.print(zws1.readString());
    zws1.close();
    File zws2 = SPIFFS.open("/charts.txt", "r");
    // Serial.println(zws2.readString());
    zws2.close();
    operateOnDataInFile("/data.txt", "Q", "R", Q);
    operateOnDataInFile("/data.txt", "kWh", "R", kWh);
    operateOnDataInFile("/data.txt", "zuoTiankWh", "R", zuoTiankWh);
  }
  Uxm = config.Uxm; // 设置蓄电池满电电压
  Uxf = config.Uxf; // 设置蓄电池浮充电压
  Ixm = config.Ixm; // 设置蓄电池满电电流
  It = config.It;   // 设置充电电流
  T = config.T;     // 设置温度保护
  Serial.print("充电电压:");
  Serial.print(Uxm / 1000);
  Serial.println("V");
  Serial.print("浮充电压:");
  Serial.print(Uxf / 1000);
  Serial.println("V");
  Serial.print("充电电流:");
  Serial.print(It / 1000);
  Serial.println("A");
  Serial.print("温度保护:");
  Serial.print(T);
  Serial.println("°C");
}
/*
 *在文件中查找指定数据并读取数据
 */
bool processFile(String targetValue, char parameterData[])
{
  Serial.println(targetValue);
  // 日期，取值1~31
  int tm_mday = timeinfo.tm_mday;
  File file = SPIFFS.open("/charts.txt", "r");
  if (!file)
  {
    Serial.println("Failed to open file");
    return false;
  }
  // 读取月份数据
  size_t i;
  String content;
  while (file.available())
  {
    content = file.readStringUntil('\n');
    if (content.startsWith(targetValue))
    {
      for (i = 0; i < tm_mday * 3 + 3; i++)
      {
        parameterData[i] = content[i];
      }
      parameterData[i++] = '\0'; // 确保字符串结束
      return true;
    }
  }

  file.close();
  return false;
}
/*
 *在文件中查找指定数据并修改后面的数据 W修改 , R读取
 */
void operateOnDataInFile(const char *filename, String targetChar, String action, float &newData)
{ // 使用 int 引用
  File file = SPIFFS.open(filename, "r+");

  if (!file)
  {
    Serial.println("Failed to open file");
    File file = SPIFFS.open(filename, "w"); // 如果文件不存在，创建新文件
  }
  if (newData > 9999.0000000) // 如果写入的数据超出存储空间
  {
    Serial.println("超出数据范围");
    return;
  }
  bool found = false; // 用于标记是否找到匹配行
  String content;
  while (file.available())
  {
    content = file.readStringUntil('\n');
    if (content.startsWith(targetChar))
    { // 使用 startsWith 更简洁地判断是否匹配
      found = true;
      if (action == "R")
      {
        Serial.print(targetChar + " = ");
        String data = content.substring(targetChar.length()); // 提取目标字符后面的数据
        newData = data.toFloat();                             // 将字符串转换为 float 并传出
        Serial.print(newData);
        Serial.print("\n");
      }
      else if (action == "W")
      {
        Serial.print(targetChar + " = ");
        Serial.print(newData);
        Serial.print("\n");
        file.seek(file.position() - (content.length() - targetChar.length()));
        while (file.peek() != '\n')
        {
          file.print(" ");
        }
        file.seek(file.position() - (content.length() - targetChar.length() - 1));
        char buffer[20];
        sprintf(buffer, "%.7f", newData);
        file.print(buffer);
      }
      break; // 找到匹配后就不再继续查找
    }
  }

  if (!found)
  { // 如果未找到匹配且是写入操作
    Serial.println("写入数据");
    file.print(targetChar);
    for (size_t i = 0; i < 13; i++)
    {
      file.print(" ");
    }
    file.print("\n");
    file.seek(file.position() - 12);
    char buffer[20];
    sprintf(buffer, "%.7f", newData);
    file.print(buffer);
  }
  // file.seek(0);
  // Serial.println(file.readString());
  file.close();
}
/*
 *计算今天还要多少秒并返回值
 */
long calculateTimeUntilMidnight(struct tm timeNow)
{
  long hoursUntilMidnight = 3600 * timeNow.tm_hour;
  long minutesUntilMidnight = 60 * timeNow.tm_min;
  long secondsUntilMidnight = timeNow.tm_sec;
  long totalSecondsUntilMidnight = 86400 - hoursUntilMidnight - minutesUntilMidnight - secondsUntilMidnight;
  Serial.printf("当前时间");
  Serial.print(asctime(&timeNow));
  Serial.printf("距离00:00还有%d秒", totalSecondsUntilMidnight);
  Serial.println("");
  return totalSecondsUntilMidnight; // 返回秒数
}

int getLastMonthDays(int currentMonth)
{
  if (currentMonth == 1)
  {
    return 31;
  }
  else if (currentMonth == 2)
  {
    int year = timeinfo.tm_year + 1900;
    if ((year % 4 == 0 && year % 100 != 0) || (year % 400 == 0))
    {
      return 29;
    }
    else
    {
      return 28;
    }
  }
  else if (currentMonth == 4 || currentMonth == 6 || currentMonth == 9 || currentMonth == 11)
  {
    return 30;
  }
  else
  {
    return 31;
  }
}

/*
 *写入图表数据函数
 */
void yourFunction()
{
  if (getLocalTime(&timeinfo))
  {
    // struct tm {
    // int tm_sec; // 秒，取值0~59；
    // int tm_min; // 分，取值0~59；
    // int tm_hour; // 时，取值0~23；
    // int tm_mday; // 月中的日期，取值1~31；
    // int tm_mon; // 月，取值0~11；
    // int tm_year; // 年，其值等于实际年份减去1900；
    // int tm_wday; // 星期，取值0~6，0为周日，1为周一，依此类推；
    // int tm_yday; // 年中的日期，取值0~365，0代表1月1日，1代表1月2日，依此类推；
    // int tm_isdst; // 夏令时标识符，实行夏令时的时候，tm_isdst为正；不实行夏令时的进候，tm_isdst为0；不了解情况时，tm_isdst()为负
    // };
    // 打开文件
    File charts = SPIFFS.open("/charts.txt", "r+"); // 储存每天用电数文件
    if (!charts)
    {
      Serial.println("Failed to open charts");
      charts = SPIFFS.open("/charts.txt", "w+"); // 如果文件不存在，创建新文件
    }
    // 月份，取值0~11
    int tm_mon = timeinfo.tm_mon + 1;
    // 日期，取值1~31
    int tm_mday = timeinfo.tm_mday;
    // 计算今天用的电量
    float jintianKWh = (kWh - zuoTiankWh);
    if (jintianKWh < 0)
    {
      return;
    }
    // 记录行数据
    String content = "";
    // 日初状态标志
    bool richu = !timerTick.active();
    // 月初状态标志
    bool yunchu = (richu && tm_mday == 1);

    if (!charts.available())
    { // 当没有记录数据初始化
      for (size_t month = 1; month <= 12; month++)
      {
        charts.printf("%02d", month);
        charts.printf("-");
        int mday = getLastMonthDays(month);
        Serial.println(mday);
        for (size_t day = 1; day <= mday; day++)
        {
          charts.printf("000");
        }
        charts.println();
      }
      charts.seek(0);
    }

    if (richu)
    {
      tm_mday--;
      if (yunchu)
      {
        if (tm_mon == 1)
        {
          tm_mon = 12;
        }
        else
        {
          tm_mon = tm_mon - 1;
        }
        tm_mday = getLastMonthDays(tm_mon);
      }
    }

    Serial.println("文件数据");
    while (charts.available())
    {
      content = charts.readStringUntil('\n');
      Serial.println(content);
    }
    charts.seek(0);

    while (charts.available())
    {
      // 记录当月份行起始位置
      int seek = charts.position();
      Serial.println("seek=" + String(seek));
      content = charts.readStringUntil('\n');
      // 查找记录当月份行
      // 转换月份数据
      char mon[5];
      sprintf(mon, "%02d", tm_mon);
      if (content.startsWith(mon))
      {
        seek = seek + 3 + ((tm_mday - 1) * 3);
        Serial.println("seek=" + String(seek));
        charts.seek(seek);
        if (jintianKWh < 10)
        {
          charts.printf("%03d", int(jintianKWh * 100));
        }
        else
        {
          charts.printf("%03d", int(jintianKWh * 10));
        }
        Serial.println("记录" + String(jintianKWh));
        // 关闭文件
        charts.close();
        break;
      }
    }
    if (richu)
    { // 更新昨天电量
      doHttpStick("今天发电: " + String(jintianKWh) + " 度");
      Q = 0;
      zuoTiankWh = kWh;
      operateOnDataInFile("/data.txt", "Q", "W", Q);
      operateOnDataInFile("/data.txt", "zuoTiankWh", "W", zuoTiankWh);
      // 月初重新记录
      if (yunchu)
      {
        doHttpStick("上月发电: " + String(kWh) + " 度");
        kWh = 0;
        zuoTiankWh = kWh;
        operateOnDataInFile("/data.txt", "kWh", "W", kWh);
        operateOnDataInFile("/data.txt", "zuoTiankWh", "W", zuoTiankWh);
      }
    }
  }
}

void diangshiqi()
{
  if (!timerTick.active() && WiFi.status() == WL_CONNECTED && getLocalTime(&timeinfo))
  {
    Serial.println("定时成功");
    long timeUntilMidnight = calculateTimeUntilMidnight(timeinfo);
    timerTick.once(timeUntilMidnight, yourFunction); // 重新设置下一天的触发
  }
}

// 看门狗定时器中断函数
void tim1Interrupt()
{
  ESP.restart(); // 看门狗定时器触发，重启设备
}

void initWatchdog()
{
  // 初始化看门狗定时器
  tim = timerBegin(1, 80, true);
  timerAttachInterrupt(tim, tim1Interrupt, true);
  timerAlarmWrite(tim, 60000 * 1000, true); // 60s触发
  timerAlarmEnable(tim);
}

const char *getResetReasonText(esp_reset_reason_t reason)
{
  switch (reason)
  {
  case ESP_RST_UNKNOWN:
    return "未知";
  case ESP_RST_POWERON:
    return "上电";
  case ESP_RST_EXT:
    return "外部";
  case ESP_RST_SW:
    return "软件";
  case ESP_RST_PANIC:
    return "异常";
  case ESP_RST_INT_WDT:
    return "中断";
  case ESP_RST_TASK_WDT:
    return "任务";
  case ESP_RST_WDT:
    return "看门";
  case ESP_RST_DEEPSLEEP:
    return "深度";
  case ESP_RST_BROWNOUT:
    return "欠压";
  case ESP_RST_SDIO:
    return "SDIO";
  default:
    return "无效";
  }
}
void setup()
{
  Serial.begin(115200); // 初始化串口通信速率
  Serial.println("初始化......");
  esp_reset_reason_t reason = esp_reset_reason();
  String Reason = getResetReasonText(reason);
  if (Reason != "上电")
  {
    rebootReason = Reason;
  }
  pinMode(spk_PIN, OUTPUT); // 设置引脚为 输出模式
  digitalWrite(spk_PIN, LOW); // 指示灯引脚
  for (size_t i = 0; i < 6; i++)
  {
    digitalWrite(spk_PIN, !digitalRead(spk_PIN)); // 指示灯引脚
    delay(50);
  }
  initWatchdog();             // 初始化看门狗
  loadConfig();               // 加载存储的信息
  initPWM();                  // 初始化PWM
  initADC();                  // 初始化ADC
  initble();                  // 初始化蓝牙
  init1602();

  startSTA(wifi_name, wifi_password); // 网络连接

  configTime(gmtOffset_sec, daylightOffset_sec, ntpServer);
}

void loop()
{
  qidongBFY();
  qidongADC();
  qidongMPPT();

  qidongble();
  qidongUDP();
  qidong1602();
  diangshiqi();

  timerWrite(tim, 0); // 喂狗操作，计时器清零
}